/* * Copyright (C) 2014 singwhatiwanna(任玉刚) <singwhatiwanna@gmail.com> * * collaborator:田啸,宋思宇,Mr.Simple * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ryg.dynamicload.internal; import java.io.File; import java.lang.reflect.Method; import java.util.HashMap; import android.annotation.TargetApi; import android.app.Activity; import android.app.Service; import android.content.Context; import android.content.ServiceConnection; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; import android.os.Build; import android.text.TextUtils; import android.util.Log; import com.ryg.dynamicload.DLBasePluginActivity; import com.ryg.dynamicload.DLBasePluginFragmentActivity; import com.ryg.dynamicload.DLBasePluginService; import com.ryg.dynamicload.DLProxyActivity; import com.ryg.dynamicload.DLProxyFragmentActivity; import com.ryg.dynamicload.DLProxyService; import com.ryg.utils.DLConstants; import com.ryg.utils.SoLibManager; import dalvik.system.DexClassLoader; public class DLPluginManager { private static final String TAG = "DLPluginManager"; /** * return value of {@link #startPluginActivity(Activity, DLIntent)} start * success */ public static final int START_RESULT_SUCCESS = 0; /** * return value of {@link #startPluginActivity(Activity, DLIntent)} package * not found */ public static final int START_RESULT_NO_PKG = 1; /** * return value of {@link #startPluginActivity(Activity, DLIntent)} class * not found */ public static final int START_RESULT_NO_CLASS = 2; /** * return value of {@link #startPluginActivity(Activity, DLIntent)} class * type error */ public static final int START_RESULT_TYPE_ERROR = 3; private static DLPluginManager sInstance; private Context mContext; private final HashMap<String, DLPluginPackage> mPackagesHolder = new HashMap<String, DLPluginPackage>(); private int mFrom = DLConstants.FROM_INTERNAL; private String mNativeLibDir = null; private int mResult; private DLPluginManager(Context context) { mContext = context.getApplicationContext(); mNativeLibDir = mContext.getDir("pluginlib", Context.MODE_PRIVATE).getAbsolutePath(); } public static DLPluginManager getInstance(Context context) { if (sInstance == null) { synchronized (DLPluginManager.class) { if (sInstance == null) { sInstance = new DLPluginManager(context); } } } return sInstance; } /** * Load a apk. Before start a plugin Activity, we should do this first.<br/> * NOTE : will only be called by host apk. * * @param dexPath */ public DLPluginPackage loadApk(String dexPath) { // when loadApk is called by host apk, we assume that plugin is invoked // by host. return loadApk(dexPath, true); } /** * @param dexPath * plugin path * @param hasSoLib * whether exist so lib in plugin * @return */ public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) { mFrom = DLConstants.FROM_EXTERNAL; PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES); if (packageInfo == null) { return null; } DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath); if (hasSoLib) { copySoLib(dexPath); } return pluginPackage; } /** * prepare plugin runtime env, has DexClassLoader, Resources, and so on. * * @param packageInfo * @param dexPath * @return */ private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) { DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName); if (pluginPackage != null) { return pluginPackage; } DexClassLoader dexClassLoader = createDexClassLoader(dexPath); AssetManager assetManager = createAssetManager(dexPath); Resources resources = createResources(assetManager); // create pluginPackage pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo); mPackagesHolder.put(packageInfo.packageName, pluginPackage); return pluginPackage; } private String dexOutputPath; private DexClassLoader createDexClassLoader(String dexPath) { File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE); dexOutputPath = dexOutputDir.getAbsolutePath(); DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader()); return loader; } private AssetManager createAssetManager(String dexPath) { try { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); addAssetPath.invoke(assetManager, dexPath); return assetManager; } catch (Exception e) { e.printStackTrace(); return null; } } public DLPluginPackage getPackage(String packageName) { return mPackagesHolder.get(packageName); } private Resources createResources(AssetManager assetManager) { Resources superRes = mContext.getResources(); Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); return resources; } /** * copy .so file to pluginlib dir. * * @param dexPath * @param hasSoLib */ private void copySoLib(String dexPath) { // TODO: copy so lib async will lead to bugs maybe, waiting for // resolved later. // TODO : use wait and signal is ok ? that means when copying the // .so files, the main thread will enter waiting status, when the // copy is done, send a signal to the main thread. // new Thread(new CopySoRunnable(dexPath)).start(); SoLibManager.getSoLoader().copyPluginSoLib(mContext, dexPath, mNativeLibDir); } /** * {@link #startPluginActivityForResult(Activity, DLIntent, int)} */ public int startPluginActivity(Context context, DLIntent dlIntent) { return startPluginActivityForResult(context, dlIntent, -1); } /** * @param context * @param dlIntent * @param requestCode * @return One of below: {@link #START_RESULT_SUCCESS} * {@link #START_RESULT_NO_PKG} {@link #START_RESULT_NO_CLASS} * {@link #START_RESULT_TYPE_ERROR} */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) { if (mFrom == DLConstants.FROM_INTERNAL) { dlIntent.setClassName(context, dlIntent.getPluginClass()); performStartActivityForResult(context, dlIntent, requestCode); return DLPluginManager.START_RESULT_SUCCESS; } String packageName = dlIntent.getPluginPackage(); if (TextUtils.isEmpty(packageName)) { throw new NullPointerException("disallow null packageName."); } DLPluginPackage pluginPackage = mPackagesHolder.get(packageName); if (pluginPackage == null) { return START_RESULT_NO_PKG; } final String className = getPluginActivityFullPath(dlIntent, pluginPackage); Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className); if (clazz == null) { return START_RESULT_NO_CLASS; } // get the proxy activity class, the proxy activity will launch the // plugin activity. Class<? extends Activity> activityClass = getProxyActivityClass(clazz); if (activityClass == null) { return START_RESULT_TYPE_ERROR; } // put extra data dlIntent.putExtra(DLConstants.EXTRA_CLASS, className); dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName); dlIntent.setClass(mContext, activityClass); performStartActivityForResult(context, dlIntent, requestCode); return START_RESULT_SUCCESS; } public int startPluginService(final Context context, final DLIntent dlIntent) { if (mFrom == DLConstants.FROM_INTERNAL) { dlIntent.setClassName(context, dlIntent.getPluginClass()); context.startService(dlIntent); return DLPluginManager.START_RESULT_SUCCESS; } fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() { @Override public void onFetch(int result, Class<? extends Service> proxyServiceClass) { // TODO Auto-generated method stub if (result == START_RESULT_SUCCESS) { dlIntent.setClass(context, proxyServiceClass); // start代理Service context.startService(dlIntent); } mResult = result; } }); return mResult; } public int stopPluginService(final Context context, final DLIntent dlIntent) { if (mFrom == DLConstants.FROM_INTERNAL) { dlIntent.setClassName(context, dlIntent.getPluginClass()); context.stopService(dlIntent); return DLPluginManager.START_RESULT_SUCCESS; } fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() { @Override public void onFetch(int result, Class<? extends Service> proxyServiceClass) { // TODO Auto-generated method stub if (result == START_RESULT_SUCCESS) { dlIntent.setClass(context, proxyServiceClass); // stop代理Service context.stopService(dlIntent); } mResult = result; } }); return mResult; } public int bindPluginService(final Context context, final DLIntent dlIntent, final ServiceConnection conn, final int flags) { if (mFrom == DLConstants.FROM_INTERNAL) { dlIntent.setClassName(context, dlIntent.getPluginClass()); context.bindService(dlIntent, conn, flags); return DLPluginManager.START_RESULT_SUCCESS; } fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() { @Override public void onFetch(int result, Class<? extends Service> proxyServiceClass) { // TODO Auto-generated method stub if (result == START_RESULT_SUCCESS) { dlIntent.setClass(context, proxyServiceClass); // Bind代理Service context.bindService(dlIntent, conn, flags); } mResult = result; } }); return mResult; } public int unBindPluginService(final Context context, DLIntent dlIntent, final ServiceConnection conn) { if (mFrom == DLConstants.FROM_INTERNAL) { context.unbindService(conn); return DLPluginManager.START_RESULT_SUCCESS; } fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() { @Override public void onFetch(int result, Class<? extends Service> proxyServiceClass) { // TODO Auto-generated method stub if (result == START_RESULT_SUCCESS) { // unBind代理Service context.unbindService(conn); } mResult = result; } }); return mResult; } /** * 获取代理ServiceClass * @param dlIntent * @param fetchProxyServiceClass */ private void fetchProxyServiceClass(DLIntent dlIntent, OnFetchProxyServiceClass fetchProxyServiceClass) { String packageName = dlIntent.getPluginPackage(); if (TextUtils.isEmpty(packageName)) { throw new NullPointerException("disallow null packageName."); } DLPluginPackage pluginPackage = mPackagesHolder.get(packageName); if (pluginPackage == null) { fetchProxyServiceClass.onFetch(START_RESULT_NO_PKG, null); return; } // 获取要启动的Service的全名 String className = dlIntent.getPluginClass(); Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className); if (clazz == null) { fetchProxyServiceClass.onFetch(START_RESULT_NO_CLASS, null); return; } Class<? extends Service> proxyServiceClass = getProxyServiceClass(clazz); if (proxyServiceClass == null) { fetchProxyServiceClass.onFetch(START_RESULT_TYPE_ERROR, null); return; } // put extra data dlIntent.putExtra(DLConstants.EXTRA_CLASS, className); dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName); fetchProxyServiceClass.onFetch(START_RESULT_SUCCESS, proxyServiceClass); } // zhangjie1980 重命名 loadPluginActivityClass -> loadPluginClass private Class<?> loadPluginClass(ClassLoader classLoader, String className) { Class<?> clazz = null; try { clazz = Class.forName(className, true, classLoader); } catch (ClassNotFoundException e) { e.printStackTrace(); } return clazz; } private String getPluginActivityFullPath(DLIntent dlIntent, DLPluginPackage pluginPackage) { String className = dlIntent.getPluginClass(); className = (className == null ? pluginPackage.defaultActivity : className); if (className.startsWith(".")) { className = dlIntent.getPluginPackage() + className; } return className; } /** * get the proxy activity class, the proxy activity will delegate the plugin * activity * * @param clazz * target activity's class * @return */ private Class<? extends Activity> getProxyActivityClass(Class<?> clazz) { Class<? extends Activity> activityClass = null; if (DLBasePluginActivity.class.isAssignableFrom(clazz)) { activityClass = DLProxyActivity.class; } else if (DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)) { activityClass = DLProxyFragmentActivity.class; } return activityClass; } private Class<? extends Service> getProxyServiceClass(Class<?> clazz) { Class<? extends Service> proxyServiceClass = null; if (DLBasePluginService.class.isAssignableFrom(clazz)) { proxyServiceClass = DLProxyService.class; } // 后续可能还有IntentService,待补充 return proxyServiceClass; } private void performStartActivityForResult(Context context, DLIntent dlIntent, int requestCode) { Log.d(TAG, "launch " + dlIntent.getPluginClass()); if (context instanceof Activity) { ((Activity) context).startActivityForResult(dlIntent, requestCode); } else { context.startActivity(dlIntent); } } private interface OnFetchProxyServiceClass { public void onFetch(int result, Class<? extends Service> proxyServiceClass); } }